using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;


namespace WindowsGame
{
    /// <summary>
    /// This is a game component that implements IUpdateable.
    /// </summary>
    public class PlayerComponent : Microsoft.Xna.Framework.GameComponent
    {
        SpriteFont debugFont;

        public enum PlayerStates
        {
            Fall,
            Jump,
            Stand,
            Run
        }
        PlayerStates currentPlayerState;
        PlayerStates oldPlayerState;

        enum PlayerDirection
        {
            Left,
            Right
        }
        PlayerDirection currentPlayerDirection;

        //Sets variables for movement-keys to easily be able to change them
        Keys jumpKey = Keys.Up;
        Keys rightKey = Keys.Right;
        Keys leftKey = Keys.Left;
        Keys shrinkKey = Keys.Space;
        KeyboardState oldKeyState;
        Color playerColor = Color.White;

        Rectangle hitBox;
        Rectangle spriteBox;
        Rectangle srcBox;
        Vector2 location;

        Texture2D playerSprite;
        Texture2D debugSprite;

        //Animation variables
        TimeSpan animationTimer;
        int animationDelay = 100;
        float animationSpeedModifier = 0.04f;

        //Set physics
        float gravity = 0.5f;
        const float xAcc = 0.5f;
        float xVel, yVel;
        float originalMaxXVel = 4;
        float maxXVel;
        const float maxYVel = 10;
        const float friction = 0.6f;
        bool jumpAllowed = false;
        float maxSize = 160;
        float originalJumpHeight = -15;
        float jumpHeight;
        float timer = 4; //total seconds for slow-effect
        bool slow = false;
        float speedModifier = 1;
        float jumpModifier = 1;
        bool collisionWithGround = false;

        //Temporarly store location in this variable to use with "hurt"
        Vector2 lastLocation;
        PlayerDirection lastDirection;
        TimeSpan hurtTimer = TimeSpan.FromMilliseconds(100);
        bool hurt = false;


        List<MapComponent.Bounds> boundList;

        public PlayerComponent(Game game)
            : base(game)
        {
            // TODO: Construct any child components here
        }

        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            currentPlayerState = new PlayerStates();
            //Start with PlayerState "Stand"
            currentPlayerState = PlayerStates.Stand;

            //Set up hitbox size and starting position
            hitBox = new Rectangle(0, 0, 40, 36);
            spriteBox = new Rectangle(0, 0, 32, 32);
            srcBox = new Rectangle(0, 0, 32, 32);
            location = new Vector2(100, 100);

            //Set up animation timer
            animationTimer = TimeSpan.FromMilliseconds(animationDelay);

            base.Initialize();
        }

        public void LoadContent(ContentManager Content)
        {
            //Load debugfont
            debugFont = Content.Load<SpriteFont>("Fonts\\SegoeUI_10");
            debugSprite = Content.Load<Texture2D>("GFX\\TestGraphic");

            //Load playersprite
            playerSprite = Content.Load<Texture2D>("GFX\\Player\\GarbagebagSprite");
        }

        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public void Update(GameTime gameTime, MapComponent map, CollectibleComponents.CollectibleSpawnerComponent collectibleSpawner, ScoreComponent score, Camera2DComponent camera)
        {
            //Check keyboardstate
            KeyboardState keyState = Keyboard.GetState();

            //Non-optimized way of getting bounds
            boundList = map.getBounds();


            //Set currentPlayerState depending on the movement of the player
            if (yVel > 0)
                currentPlayerState = PlayerStates.Fall;
            else if (yVel < 0)
                currentPlayerState = PlayerStates.Jump;
            else if (Math.Abs(xVel) > 0 && collisionWithGround)
                currentPlayerState = PlayerStates.Run;
            else if (collisionWithGround)
                currentPlayerState = PlayerStates.Stand;

            collisionWithGround = false;


            //Add gravity to player
            gravity = 0.5f;
            if (yVel < maxYVel)
                yVel += gravity;

            //Sets max speed
            maxXVel = originalMaxXVel * speedModifier;

            //Sets jump height
            jumpHeight = originalJumpHeight * jumpModifier;

            //Hitting the the camera view's left wall
            if (hitBox.X < camera.getPosition().X)
                xVel = 0; //Stops the player

            //Moving character on x-axis
            if (keyState.IsKeyDown(rightKey))
            {
                if (xVel < maxXVel)
                    xVel += xAcc;

                //Set direction. This is going to be used later on when drawing character
                currentPlayerDirection = PlayerDirection.Right;
            }

            else if (keyState.IsKeyDown(leftKey))
            {

                if (xVel > -maxXVel && hitBox.X > camera.getPosition().X) //only move left if within the camera view
                    xVel -= xAcc;

                //Set direction
                currentPlayerDirection = PlayerDirection.Left;
            }
            else
            {
                //Equalize movement to simulate friction
                if (xVel > 1)
                    xVel -= friction;
                else if (xVel < -1)
                    xVel += friction;
                else if (xVel <= 1 && xVel >= -1) //Set to 0 if between -1 and 1. To prevent "sliding" glitch
                    xVel = 0;
            }
            

            //Move character
            //Use hurt bool and hurt timer to allow for faster speeds for a short amount of time
            if (!hurt)
            {
                if (xVel <= maxXVel && xVel >= -maxXVel)
                    location.X += xVel;
                else //Prevents going faster than the max speed
                {
                    if (xVel > 0)
                        xVel = maxXVel;
                    if (xVel < 0)
                        xVel = -maxXVel;
                }
            }
            else
                location.X += xVel;


            for (int i = 0; i < boundList.Count; i++)
            {
                //Check if the player intersects with anything from the bound list
                if (hitBox.Intersects(boundList[i].rect))
                {
                    //Keep the player from going through solid objects
                    if (boundList[i].type == MapComponent.BoundType.Solid)
                    {
                        collisionWithGround = true;

                        if (hitBox.Bottom > boundList[i].rect.Top && hitBox.Bottom < boundList[i].rect.Top + 15 && yVel > 0)
                        {
                            location.Y = boundList[i].rect.Top - hitBox.Height + 1;
                            yVel = 0;
                            jumpAllowed = true;
                            gravity = 0; //To prevent jumping glitch when setting hitbox.Y = (int)location.Y
                        }

                        //Hitting solid object from the side
                        else if (hitBox.Bottom > boundList[i].rect.Top + 10)
                        {
                            //From the right
                            if (hitBox.Left < boundList[i].rect.Right && hitBox.Left > boundList[i].rect.Right - 10 && xVel < 0)
                                location.X = boundList[i].rect.Right;
                            
                            //From the left
                            else if (hitBox.Right > boundList[i].rect.Left && hitBox.Right < boundList[i].rect.Left + 10 && xVel > 0)
                                location.X = boundList[i].rect.Left - hitBox.Width;
                        }

                        //Hitting solid object from below
                        else if (hitBox.Top < boundList[i].rect.Bottom && hitBox.Top > boundList[i].rect.Bottom - 15 && yVel < 0)
                        {
                            location.Y = boundList[i].rect.Bottom;
                            yVel = 0;
                        }
                    }

                    //Keep the player from going through "passable" objects, except if jumping onto it.
                    else if (boundList[i].type == MapComponent.BoundType.Passable)
                    {
                        collisionWithGround = true;

                        if (currentPlayerState != PlayerStates.Jump && hitBox.Bottom > boundList[i].rect.Top && hitBox.Bottom < boundList[i].rect.Top + 10)
                        {
                            location.Y = boundList[i].rect.Top - hitBox.Height + 1;
                            yVel = 0;
                            jumpAllowed = true;
                        }
                    }

                    //Reaching the goal
                    else if (boundList[i].type == MapComponent.BoundType.Goal)
                    {
                        map.changeMap(map.GetGoalString()) ;
                    }

                    //Hitting objects with bound type "hurt"
                    else if (boundList[i].type == MapComponent.BoundType.Hurt)
                    {
                        score.RemovePoints(4);

                        //Use hurt bool and hurt timer to allow for faster speeds for a short amount of time
                        hurt = true;

                        //If player falls, use old location to place him where he left off.
                        //Keep in mind which direction he was facing and offset the old location to prevent glitches
                        if (currentPlayerState == PlayerStates.Fall)
                        {
                            if (lastDirection == PlayerDirection.Right)
                                location.X = lastLocation.X - spriteBox.Width;
                            if (lastDirection == PlayerDirection.Left)
                                location.X = lastLocation.X + spriteBox.Width;
                            location.Y = lastLocation.Y;

                            yVel = 0;
                        }
                        else
                        {
                            //Else, just push him bakwards

                            //From the right
                            if (hitBox.Left < boundList[i].rect.Right && hitBox.Left > boundList[i].rect.Right - 20)
                            {
                                location.X = boundList[i].rect.Right + 1;
                                xVel = 7;
                                yVel = -5;
                            }

                            //From the left
                            else if (hitBox.Right > boundList[i].rect.Left && hitBox.Right < boundList[i].rect.Left + 20)
                            {
                                location.X = boundList[i].rect.Left - hitBox.Width - 1;
                                xVel = -7;
                                yVel = -5;
                            }

                            //From bottom. Just push him down
                            else if (hitBox.Top < boundList[i].rect.Bottom && hitBox.Top > boundList[i].rect.Bottom - 20)
                            {
                                yVel = 0;
                                location.Y = boundList[i].rect.Bottom;
                            }

                        }

                        //Make the player smaller
                        if (spriteBox.Height > 32 && spriteBox.Width > 32)
                        {
                            spriteBox.Width -= 2;
                            spriteBox.Height -= 2;
                            hitBox.Width -= 1;
                            hitBox.Height -= 2;

                            //Move character
                            location.Y += 2;
                            location.X += 0.5f;


                            animationSpeedModifier -= 0.02f;
                        }

                        //Raise the jump height
                        if (jumpHeight > -15)
                            originalJumpHeight -= 0.1f;

                        //Raise the running speed
                        if (maxXVel < 4)
                            originalMaxXVel += 0.05f;

                    }

                    //Hitting objects with bound type "slow"
                    else if (boundList[i].type == MapComponent.BoundType.Slow && slow == false)
                    {
                        slow = true;
                    }

                }

            }

            //After intersecting with objects which slows you down
            if (slow == true)
            {
                //Set up timer
                float elapsedTime = 0;
                elapsedTime = (float)gameTime.ElapsedGameTime.TotalSeconds;

                //Timer on
                if (timer > 0)
                {
                    timer -= elapsedTime;
                    speedModifier = 0.4f;
                    jumpModifier = 0.8f;
                }

                //Timer off
                if (timer <= 0)
                {
                    speedModifier = 1;
                    jumpModifier = 1;
                    timer = 4; //Reset timer
                    slow = false;
                }

                //Add color to player to see if he is slowed
                playerColor = Color.Yellow;
            }
            else
                playerColor = Color.White;

            //Hurt timer
            //Use hurt bool and hurt timer to allow for faster speeds for a short amount of time
            if (hurt)
            {
                hurtTimer -= gameTime.ElapsedGameTime;
                
                if (hurtTimer.Milliseconds <= 0)
                {
                    hurt = false;
                    //Reset timer
                    hurtTimer = TimeSpan.FromMilliseconds(100);
                }

                playerColor = Color.Red; //Blink player red if he gets hurt
            }
            //Store old location when running
            if (!hurt)
            {
                if (currentPlayerState == PlayerStates.Run)
                {
                    lastLocation = location;
                    lastDirection = currentPlayerDirection;
                }
            }

            //Jumping
            if (keyState.IsKeyDown(jumpKey) && jumpAllowed)
            {
                yVel = jumpHeight;
                jumpAllowed = false; //denying double-jump
            }

            //Do not allow for jump when falling
            if (currentPlayerState == PlayerStates.Fall)
                jumpAllowed = false;


            //Check for collision with colletives
            for (int i = 0; i < collectibleSpawner.GetCollectibleList().Count; i++)
            {
                //If intersecting with player
                if (hitBox.Intersects(collectibleSpawner.GetCollectibleList()[i].GetHitbox()))
                {
                    //Despawn collective
                    collectibleSpawner.Despawn(i);

                    score.AddPoints(1);

                    //Make the player bigger
                    if (spriteBox.Height <= maxSize && spriteBox.Width <= maxSize)
                    {
                        spriteBox.Width += 2;
                        spriteBox.Height += 2;
                        //Use half of height to get rid of glitches involving too large hitbox
                        hitBox.Width += 1;
                        hitBox.Height += 2;

                        //Move character
                        location.Y -= 2;
                        location.X -= 0.5f;


                        animationSpeedModifier += 0.02f;
                    }
                    
                    //Lower the jump height
                    if (jumpHeight < -10)
                        originalJumpHeight += 0.1f;

                    //Lower the running speed
                    if (maxXVel > 2)
                        originalMaxXVel -= 0.05f;
                }

            }

            //Remove points and size
            if (keyState.IsKeyDown(shrinkKey) && oldKeyState != keyState)
            {
                score.RemovePoints(1);

                //Make the player smaller
                if (spriteBox.Height > 32 && spriteBox.Width > 32)
                {
                    spriteBox.Width -= 4;
                    spriteBox.Height -= 4;
                    hitBox.Width -= 2;
                    hitBox.Height -= 4;

                    //Move character
                    location.Y += 4;
                    location.X += 1;


                    animationSpeedModifier -= 0.04f;
                }

                //Raise the jump height
                if (jumpHeight > -15)
                    originalJumpHeight -= 0.2f;

                //Raise the running speed
                if (maxXVel <= 4)
                    originalMaxXVel += 0.1f;
            }

            #region debug
            //Debug
            if (keyState.IsKeyDown(Keys.S))
            {
                    //Make the player bigger
                    if (spriteBox.Height <= maxSize && spriteBox.Width <= maxSize)
                    {
                        spriteBox.Width += 2;
                        spriteBox.Height += 2;
                        hitBox.Width += 1;
                        hitBox.Height += 2;

                        //Move character
                        location.Y -= 2;
                        location.X -= 0.5f;


                        animationSpeedModifier += 0.02f;
                    }
                
                    //Lower the jump height
                    if (jumpHeight < -10)
                        originalJumpHeight += 0.1f;

                    //Lower the running speed
                    if (maxXVel > 2)
                        originalMaxXVel -= 0.05f;
            }

            else if (keyState.IsKeyDown(Keys.A))
            {
                    //Make the player smaller
                    if (spriteBox.Height > 32 && spriteBox.Width > 32)
                    {
                        spriteBox.Width -= 2;
                        spriteBox.Height -= 2;
                        hitBox.Width -= 1;
                        hitBox.Height -= 2;

                        //Move character
                        location.Y += 2;
                        location.X += 0.5f;


                        animationSpeedModifier -= 0.02f;
                    }

                    //Raise the jump height
                    if (jumpHeight > -15)
                        originalJumpHeight -= 0.1f;

                    //Raise the running speed
                    if (maxXVel <= 4)
                        originalMaxXVel += 0.05f;
            }
            #endregion

            //Calculate new location
            location.Y += yVel;

            //Places the hitbox to the location of the player
            hitBox.X = (int)location.X;
            hitBox.Y = (int)location.Y;
            spriteBox.X = (int)hitBox.X + (hitBox.Width / 2 - spriteBox.Width / 2);
            spriteBox.Y = (int)hitBox.Y + 4;

            //Animate player
            AnimatePlayer(gameTime, currentPlayerState);
            
            oldPlayerState = currentPlayerState; //Store playerstate for next frame to check if state is changed

            //Store old key state to prevent holding keys
            oldKeyState = keyState;
            base.Update(gameTime);
        }


        public void Draw(SpriteBatch spriteBatch, Camera2DComponent camera)
        {
            spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend, SamplerState.PointClamp, null, null, null, camera.getMatrix());

            //Draw player dependant on which way he moves
            if (currentPlayerDirection == PlayerDirection.Right)
                spriteBatch.Draw(playerSprite, spriteBox, srcBox, playerColor);
            else if (currentPlayerDirection == PlayerDirection.Left)
                spriteBatch.Draw(playerSprite, spriteBox, srcBox, playerColor, 0f, new Vector2(0, 0), SpriteEffects.FlipHorizontally, 1); //Flip when running other way round
            
            //spriteBatch.Draw(debugSprite, hitBox, Color.Multiply(Color.Black, 0.3f));

            spriteBatch.End();

            //Debugging
            spriteBatch.Begin();
            spriteBatch.DrawString(debugFont, "Current playerstate: " + currentPlayerState.ToString(), Vector2.Zero, Color.Black);
            spriteBatch.DrawString(debugFont, "Y velocity: " + yVel.ToString(), new Vector2(0, 10), Color.Black);
            spriteBatch.DrawString(debugFont, "X velocity: " + xVel.ToString(), new Vector2(0, 20), Color.Black);
            spriteBatch.DrawString(debugFont, "Jump height: " + jumpHeight.ToString(), new Vector2(0, 30), Color.Black);
            spriteBatch.DrawString(debugFont, "Max x velocity: " + maxXVel.ToString(), new Vector2(0, 40), Color.Black);
            spriteBatch.DrawString(debugFont, "Hitbox size: " + hitBox.Height.ToString(), new Vector2(0, 50), Color.Black);
            spriteBatch.DrawString(debugFont, "Sprite box size: " + spriteBox.Height.ToString(), new Vector2(0, 60), Color.Black);
            spriteBatch.DrawString(debugFont, "Animation timer modifier: " + animationSpeedModifier.ToString(), new Vector2(0, 70), Color.Black);
            spriteBatch.DrawString(debugFont, "Timer (Slow): " + timer.ToString(), new Vector2(0, 80), Color.Black);
            spriteBatch.DrawString(debugFont, "Timer (Hurt): " + hurtTimer.ToString(), new Vector2(0, 90), Color.Black);
            spriteBatch.DrawString(debugFont, "Last location: " + lastLocation.ToString(), new Vector2(0, 100), Color.Black);

            spriteBatch.End();
        }

        //Animate player sprite
        public void AnimatePlayer(GameTime gameTime, PlayerStates currentPlayerState)
        {
            //Temporarly value for controlling animation timer
            float tempModifier = 1;

            //If playerstate has changed
            if (oldPlayerState != currentPlayerState)
                srcBox.X = 0; //Reset to first frame

            //Decide which animation to play
            switch (currentPlayerState)
            {
                case PlayerStates.Fall:
                    srcBox.Y = 64; //Last row of spritesheet
                    break;
                case PlayerStates.Jump:
                    srcBox.Y = 64; //Last row of spritesheet
                    break;
                case PlayerStates.Stand:
                    tempModifier = 1;
                    srcBox.Y = 0; //First row of spritesheet
                    break;
                case PlayerStates.Run:
                    tempModifier = animationSpeedModifier;
                    srcBox.Y = 32; //Second row of spritesheet
                    break;
                default:
                    break;
            }

            //Move sourcebox
            if (animationTimer.Milliseconds <= 0)
            {
                if (srcBox.X < playerSprite.Width - srcBox.Width)
                    srcBox.X += 32;
                else
                    srcBox.X = 0;
                animationTimer = TimeSpan.FromMilliseconds(animationDelay * tempModifier);
            }
            else
                animationTimer -= gameTime.ElapsedGameTime;
        }

        public void Spawn(Vector2 value)
        {
            this.location = value;
            hitBox.Width = 40;
            hitBox.Height = 36;
            spriteBox.Width = 32;
            spriteBox.Height = 32;
            originalMaxXVel = 4;
            originalJumpHeight = -15;
        }

        public Vector2 GetLocation() { return this.location; }

        public void SetLocation(Vector2 value) { this.location = value; }

        public Vector2 GetSize() { return new Vector2(this.spriteBox.Width, this.spriteBox.Height); }

        public float GetXVel() { return this.xVel; }
    }
}
